﻿using AKBGame.Object;
using AKBGame.Util;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AKBGame.Service
{
    public class SongCalculator
    {
        public static List<Member> CalculateBestUnit(ref Song song, List<Member> memberList, bool includeGuest, bool includeThirdColor, int guestPerformance, int filterPerformance, bool ignoreFilter)
        {
            //filter the memberList by color
            List<Member> filterMemberList = new List<Member>();

            if (!ignoreFilter)
            {
                int redColorCount = song.RhythmDataList.Count(o => o.ColorType == MemberColorHelper.Color.Red);
                int blueColorCount = song.RhythmDataList.Count(o => o.ColorType == MemberColorHelper.Color.Blue);
                int greenColorCount = song.RhythmDataList.Count(o => o.ColorType == MemberColorHelper.Color.Green);
                int yellowColorCount = song.RhythmDataList.Count(o => o.ColorType == MemberColorHelper.Color.Yellow);
                int purpleColorCount = song.RhythmDataList.Count(o => o.ColorType == MemberColorHelper.Color.Purple);

                int[] countArray = new int[] { redColorCount, blueColorCount, greenColorCount, yellowColorCount, purpleColorCount };

                //find out the largest three count
                int largestCount1 = (from number in countArray
                                     orderby number descending
                                     select number).Distinct().First();

                int largestCount2 = (from number in countArray
                                     orderby number descending
                                     select number).Distinct().Skip(1).First();

                int largestCount3 = includeThirdColor ? (from number in countArray
                                                         orderby number descending
                                                         select number).Distinct().Skip(2).First() : -1;

                if (redColorCount == largestCount1 || redColorCount == largestCount2 || redColorCount == largestCount3)
                {
                    filterMemberList.AddRange(memberList.Where(o => o.Color == MemberColorHelper.GetMemberColor(MemberColorHelper.Color.Red)));
                }

                if (blueColorCount == largestCount1 || blueColorCount == largestCount2 || blueColorCount == largestCount3)
                {
                    filterMemberList.AddRange(memberList.Where(o => o.Color == MemberColorHelper.GetMemberColor(MemberColorHelper.Color.Blue)));
                }

                if (greenColorCount == largestCount1 || greenColorCount == largestCount2 || greenColorCount == largestCount3)
                {
                    filterMemberList.AddRange(memberList.Where(o => o.Color == MemberColorHelper.GetMemberColor(MemberColorHelper.Color.Green)));
                }

                if (yellowColorCount == largestCount1 || yellowColorCount == largestCount2 || yellowColorCount == largestCount3)
                {
                    filterMemberList.AddRange(memberList.Where(o => o.Color == MemberColorHelper.GetMemberColor(MemberColorHelper.Color.Yellow)));
                }

                if (purpleColorCount == largestCount1 || purpleColorCount == largestCount2 || purpleColorCount == largestCount3)
                {
                    filterMemberList.AddRange(memberList.Where(o => o.Color == MemberColorHelper.GetMemberColor(MemberColorHelper.Color.Purple)));
                }

                //filter the memberList by performace
                filterMemberList = filterMemberList.Where(o => o.Performance + o.PerformanceBoost >= filterPerformance).ToList();

                //7, 6 star and 5 star with score up
                filterMemberList = filterMemberList.Where(o => o.Rarity >= 6 || (o.Rarity >= 5 && 1 <= o.SkillEffectID && o.SkillEffectID <= 5)).ToList();
            }

            else
            {
                filterMemberList.AddRange(memberList);
            }

            List<List<Member>> combinationList = GetCombinationOfMemberList(filterMemberList, 5);
            List<Member> guestList = GetGuestList(song, guestPerformance);
            //List<List<Member>> combinationList = GetPermutationOfMemberList(filterMemberList, 5);

            List<Member> bestUnit = new List<Member>();
            double bestScore = 0;

            foreach (List<Member> combinationMemberList in combinationList)
            {
                //if the list contain duplicate member, skip
                if (combinationMemberList.Select(o => o.MemberName).Distinct().Count() != 5)
                {
                    continue;
                }

                CalculateScore(song, ref bestUnit, ref bestScore, combinationMemberList);

                if (includeGuest)
                {
                    foreach (Member guest in guestList)
                    {
                        List<Member> copyOfMemberList = new List<Member>();
                        copyOfMemberList.AddRange(combinationMemberList);
                        copyOfMemberList.Add(guest);

                        CalculateScore(song, ref bestUnit, ref bestScore, copyOfMemberList);
                    }
                }
                else
                {
                    CalculateScore(song, ref bestUnit, ref bestScore, combinationMemberList);
                }
            }

            //if (includeGuest)
            //{
            //    List<Member> bestUnitWithGuest = new List<Member>();
            //    bestUnitWithGuest.AddRange(bestUnit);

            //    foreach (Member guest in guestList)
            //    {
            //        List<Member> copyOfMemberList = new List<Member>();
            //        copyOfMemberList.AddRange(bestUnitWithGuest);
            //        copyOfMemberList.Add(guest);

            //        CalculateScore(song, ref bestUnit, ref bestScore, copyOfMemberList);
            //    }
            //}

            return bestUnit;
        }

        private static void CalculateScore(Song song, ref List<Member> bestUnit, ref double bestScore, List<Member> combinationMemberList)
        {
            double totalScore = 0;

            double totalPerformance = 0;
            combinationMemberList.ForEach(o => totalPerformance += (o.Performance + o.PerformanceBoost));

            //color bonus
            double redColorBonus = 1 + 0.1 * (combinationMemberList.Where(o => o.Color == MemberColorHelper.GetMemberColor(MemberColorHelper.Color.Red)).Count());
            double blueColorBonus = 1 + 0.1 * (combinationMemberList.Where(o => o.Color == MemberColorHelper.GetMemberColor(MemberColorHelper.Color.Blue)).Count());
            double greenColorBonus = 1 + 0.1 * (combinationMemberList.Where(o => o.Color == MemberColorHelper.GetMemberColor(MemberColorHelper.Color.Green)).Count());
            double yellowColorBonus = 1 + 0.1 * (combinationMemberList.Where(o => o.Color == MemberColorHelper.GetMemberColor(MemberColorHelper.Color.Yellow)).Count());
            double purpleColorBonus = 1 + 0.1 * (combinationMemberList.Where(o => o.Color == MemberColorHelper.GetMemberColor(MemberColorHelper.Color.Purple)).Count());

            //cloth bonus
            double clothBonus = 1;
            foreach (Member member in combinationMemberList)
            {
                if (song.ClothBonusIDList.Any(o => o == member.ClothID))
                {
                    clothBonus += 0.05;
                }
            }

            //skill bonus
            List<SkillBonus> skillBonusList = CalculateSkillBonus(combinationMemberList, song.RhythmDataList);
            int combo = 1;
            List<RhythmData> rhythmDataListCopy = new List<RhythmData>();
            song.RhythmDataList.ForEach(o => rhythmDataListCopy.Add((RhythmData)o.Clone()));

            foreach (RhythmData rhythmData in rhythmDataListCopy)
            {
                double colorBonus = 1;
                double skillBonus = 1;
                double mainColorBonus = 0;
                double comboBonus = 1;
                double tapBonus = 1;

                //color bonus
                if (rhythmData.ColorType == MemberColorHelper.Color.Red)
                    colorBonus = redColorBonus;
                else if (rhythmData.ColorType == MemberColorHelper.Color.Blue)
                    colorBonus = blueColorBonus;
                else if (rhythmData.ColorType == MemberColorHelper.Color.Green)
                    colorBonus = greenColorBonus;
                else if (rhythmData.ColorType == MemberColorHelper.Color.Yellow)
                    colorBonus = yellowColorBonus;
                else if (rhythmData.ColorType == MemberColorHelper.Color.Purple)
                    colorBonus = purpleColorBonus;

                //skill bonus
                foreach (SkillBonus sb in skillBonusList)
                {
                    if (sb.StartTime < rhythmData.Time && rhythmData.Time <= sb.EndTime)
                    {
                        skillBonus = sb.Bonus;
                        rhythmData.ActiveSkillID = sb.ActiveSkillID;
                        break;
                    }

                    else
                    {
                        rhythmData.ActiveSkillID = -1;
                    }
                }

                //main color bonus (Assume Unbelievable and Excellent)
                if (rhythmData.ColorType == song.MainColor)
                    mainColorBonus = 0.00375;
                else
                    mainColorBonus = 0.003124; //0.00314;

                //combo bonus
                if (combo >= 200)
                    comboBonus = 1.2;
                else if (combo >= 100)
                    comboBonus = 1.15;
                else if (combo >= 50)
                    comboBonus = 1.1;
                else
                    comboBonus = 1;

                //tap bonus
                if (rhythmData.TapType == TapTypeHelper.TapType.LongStart || rhythmData.TapType == TapTypeHelper.TapType.LongEnd)
                    tapBonus = 1.2;
                else if (rhythmData.TapType == TapTypeHelper.TapType.ArrowRight || rhythmData.TapType == TapTypeHelper.TapType.ArrowLeft || rhythmData.TapType == TapTypeHelper.TapType.ArrowUp)
                    tapBonus = 1.25;
                else
                    tapBonus = 1;

                double rhythmScore = totalPerformance * mainColorBonus * colorBonus * clothBonus * skillBonus * comboBonus * tapBonus;
                rhythmData.Score = rhythmScore;
                totalScore += rhythmScore;

                combo++;
            }

            if (totalScore > bestScore)
            {
                bestScore = totalScore;
                bestUnit = combinationMemberList;
                song.RhythmDataList = rhythmDataListCopy;
            }
        }

        private static List<Member> GetGuestList(Song song, int guestPerformance)
        {
            List<Member> memberList = new List<Member>();
            int bonusClothID = -1;

            if(song.ClothBonusIDList.Count > 0) {
                bonusClothID = song.ClothBonusIDList[0];
            }

            for (int j = 1; j <= 10; j++)
            {
                Member member = new Member();
                member.MemberName = "Guest";
                member.ClothID = bonusClothID;
                member.Color = MemberColorHelper.GetMemberColor(song.MainColor);
                member.Performance = guestPerformance;
                member.SkillEffectID = 5;
                member.SkillEffect = "100% (7Sec)";
                member.SkillTriggerID = j;
                member.SkillTrigger = (1 <= j && j <= 5) ? 20 * j + " Combo" : 15 * ((member.SkillTriggerID % 6) + 1) + " Sec";
                memberList.Add(member);
            }
            

            return memberList;
        }

        private static List<List<Member>> GetPermutationOfMemberList(List<Member> memberList, int numberOfItems)
        {
            if (numberOfItems == 1)
            {
                List<List<Member>> permutationMemberList = new List<List<Member>>();

                for (int i = 0; i < memberList.Count; i++)
                {
                    permutationMemberList.Add(new List<Member>() { memberList[i] });
                }

                return permutationMemberList;
            }

            else
            {
                List<List<Member>> permutationMemberList = new List<List<Member>>();

                for (int i = 0; i < memberList.Count; i++)
                {
                    List<Member> copyOfMemberList = new List<Member>();
                    copyOfMemberList.AddRange(memberList);

                    Member member = copyOfMemberList[i];
                    copyOfMemberList.RemoveAt(i);

                    List<List<Member>> tempPermutationMemberList = GetPermutationOfMemberList(copyOfMemberList, numberOfItems - 1);
                    tempPermutationMemberList.ForEach(o => o.Add(member));
                    permutationMemberList.AddRange(tempPermutationMemberList);
                }

                return permutationMemberList;
            }
        }

        // Time complexity: O(kn), where n = total number of items, k = pick up number of items
        // (n,k)
        // (n-1, k-1) (1,1)
        // (n-2, k-2) (1,1)
        // ...
        // (n+1-k, 1) (1,1)
        private static List<List<Member>> GetCombinationOfMemberList(List<Member> memberList, int numberOfItems)
        {
            if (numberOfItems == 1)
            {
                List<List<Member>> combinationMemberList = new List<List<Member>>();

                for (int i = 0; i < memberList.Count; i++)
                {
                    combinationMemberList.Add(new List<Member>() { memberList[i] });
                }

                return combinationMemberList;
            }

            else
            {
                List<List<Member>> combinationMemberList = new List<List<Member>>();

                for (int i = 0; i < memberList.Count; i++)
                {
                    List<Member> copyOfMemberList = new List<Member>();
                    copyOfMemberList.AddRange(memberList);

                    Member member = copyOfMemberList[i];

                    List<List<Member>> tempCombinationMemberList = GetCombinationOfMemberList(copyOfMemberList.Skip(i + 1).ToList(), numberOfItems - 1);
                    tempCombinationMemberList.ForEach(o => o.Add(member));
                    combinationMemberList.AddRange(tempCombinationMemberList);
                }

                return combinationMemberList;
            }
        }

        private static List<SkillBonus> CalculateSkillBonus(List<Member> memberList, List<RhythmData> rhythmData)
        {
            List<SkillBonus> skillBonusList = new List<SkillBonus>();

            //calculate skill bonus
            foreach (Member member in memberList)
            {
                SkillBonus skillBonus = GetSkillBonus(member, rhythmData);
                skillBonusList.Add(skillBonus);
            }
            
            //Check overlap
            SortSkillBonus(ref skillBonusList);
            return skillBonusList;
        }

        private static SkillBonus GetSkillBonus(Member member, List<RhythmData> rhythmData)
        {
            SkillBonus skillBonus = new SkillBonus();
            skillBonus.StartTime = GetSkillTriggerStartTime(member, rhythmData);
            skillBonus.EndTime = GetSkillTriggerEndTime(member, skillBonus.StartTime);
            skillBonus.Bonus = GetSkillBonusPercentage(member);
            skillBonus.ActiveSkillID = member.SkillEffectID;

            return skillBonus;
        }

        private static float GetSkillTriggerStartTime(Member member, List<RhythmData> rhythmData)
        {
            float startTime;

            //Combo
            if (1 <= member.SkillTriggerID && member.SkillTriggerID <= 5)
            {
                int combo = 20 * member.SkillTriggerID;

                if (rhythmData.Count < combo)
                    startTime = -1;

                else
                {
                    startTime = rhythmData.First(o => o.Time >= rhythmData[combo - 1].Time + 1.8).Time;
                }
            }

            //Second
            else if (6 <= member.SkillTriggerID && member.SkillTriggerID <= 10)
            {
                float second = (float)(15 * ((member.SkillTriggerID % 6) + 1) + 1.8);
                startTime = rhythmData.First(o => o.Time >= second).Time;
            }

            else
            {
                startTime = -1;
            }

            return startTime;
        }

        private static float GetSkillTriggerEndTime(Member member, float startTime)
        {
            float endTime;

            if (startTime == -1)
                endTime = -1;

            else if (member.SkillEffect.Contains("Sec"))
            {
                int duration = int.Parse(member.SkillEffect.Split(new string[] { "(", "Sec)" }, StringSplitOptions.None)[1]);
                endTime = startTime + duration;
            }

            else
            {
                endTime = -1;
            }

            return endTime;
        }

        private static double GetSkillBonusPercentage(Member member)
        {
            double bonusPercentage = 1;

            if (member.SkillEffectID == 1)
                bonusPercentage = 1.75;
            else if (member.SkillEffectID == 2)
                bonusPercentage = 1.8;
            else if (member.SkillEffectID == 3)
                bonusPercentage = 1.85;
            else if (member.SkillEffectID == 4)
                bonusPercentage = 1.9;
            else if (member.SkillEffectID == 5)
                bonusPercentage = 2;

            return bonusPercentage;
        }

        private static void SortSkillBonus(ref List<SkillBonus> skillBonusList)
        {
            skillBonusList = skillBonusList.OrderBy(o => o.StartTime).ToList();

            for (int i = 1; i < skillBonusList.Count; i++)
            {
                if (skillBonusList[i].StartTime < skillBonusList[i - 1].EndTime)
                {
                    float diff = skillBonusList[i].EndTime - skillBonusList[i].StartTime;
                    skillBonusList[i].StartTime = skillBonusList[i - 1].EndTime + 0.14f;
                    skillBonusList[i].EndTime = skillBonusList[i].StartTime + diff;
                }
            }
        }
    }
}
